package wortubung.lang.de;

import static wortubung.lang.LanguageManager.*;
import static wortubung.lang.de.DeutschSpracheWerkzeug.*;
import static wortubung.util.LatinScript.*;
import static wortubung.util.CommonUtils.*;
import static wortubung.util.JSONUtils.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Properties;

import org.json.JSONArray;
import org.json.JSONObject;

import wortubung.core.LANG;
import wortubung.core.POS;
import wortubung.core.POSInfo;
import wortubung.inflection.DeclensionResult;

// http://www.germanveryeasy.com/adjective-declension

public class Deklination {

	////////////////////////////////////////////////////////////////////////
	// Declining nouns and other words

	/**
	 * @return String[N,G,D,A][s,p]
	 */
	public static DeclensionResult declineNoun(String noun, POSInfo posInfo) {
		return NomenDeklinationRegel.decline(noun, posInfo);
	}

	public static DeclensionResult[] declineWord(String word) {
		WordDeclension wd = wordDeclensions.get(word);
		if (wd != null)
			return decline(wd);
		return declineAdjective(word);
	}

	////////////////////////////////////////////////////////////////////////
	// Comparative and superlative of adjectives and adverbs

	private static final HashSet<String> singleSyllableComparativeWithoutUmlaut = new HashSet<String>();
	private static final HashMap<String,String[]> irregularComparative = new HashMap<String,String[]>();
	private static final HashMap<String,String> superlativeSpecialEnding = new HashMap<String,String>();
	private static final HashMap<String,String> comparativeSpecialEnding = new HashMap<String,String>();

	/**
	 * http://www.germanveryeasy.com/comparative-and-superlative
	 * 
	 * @return String[]{ comparative, singular superlative }
	 */
	public static String[] getComparativeSuperlative(String adjadv) {
		String[] sa = irregularComparative.get(adjadv);
		if (sa != null)
			return sa;

		if (!singleSyllableComparativeWithoutUmlaut.contains(adjadv))
			adjadv = singleSyllableAddUmlaut(adjadv);

		String x = comparativeSpecialEnding.get(adjadv);
		String comp;
		if (x != null)
			comp = x;
		else if (adjadv.endsWith("er"))
			comp = chopRight(adjadv, 2) + "(e)rer";
		else
			comp = appendSansDupE(adjadv, "er");

		String superI;
		x = superlativeSpecialEnding.get(adjadv);
		if (x != null)
			superI = adjadv + x;
		else
			superI = appendWithExtraE(adjadv, "ste", POS.ADJ);

		return new String[]{ comp, superI, "am " + superI + 'n' };
	}

	////////////////////////////////////////////////////////////////////////
	// Implementation of declensions of pronouns, articles, and adjectives

	private enum AdjectiveAndAdverbKey {
		SingleSyllableComparativeWithoutUmlaut,
		SuperlativeSpecialEnding,
		ComparativeSpecialEnding,
		IrregularComparatives
	};

	private static final HashMap<DeclSuffixKey,DeclensionResult> declSuffices = new HashMap<DeclSuffixKey,DeclensionResult>();
	private enum DeclSuffixKey {
		DER_SUFFIX,         // only for "der"
		MAND_SUFFIX,        // *mand words: jemand, niemand, iemand
	    DER_WORD_SUFFIX,    // for der-words
	    EIN_WORD_SUFFIX,    // for ein-words
	    POSSESSIVE_SUFFIX,  // for possessive pronouns
	    ADJ_DER_SUFFIX,     // for adjective, with definite article
	    ADJ_EIN_SUFFIX,     // for adjective, with indefinite article
		ADJ_STARKE_SUFFIX,  // for adjective, without article
		PERSONAL_PRONOUNS,
		REFLEXIVE_PRONOUNS
	};
	public static DeclensionResult getDerSuffix()           { return declSuffices.get(DeclSuffixKey.DER_SUFFIX); }
	public static DeclensionResult getMandSuffix()          { return declSuffices.get(DeclSuffixKey.MAND_SUFFIX); }
	public static DeclensionResult getDerTypeSuffix()       { return declSuffices.get(DeclSuffixKey.DER_WORD_SUFFIX); }
	public static DeclensionResult getEinTypeSuffix()       { return declSuffices.get(DeclSuffixKey.EIN_WORD_SUFFIX); }
	public static DeclensionResult getDerAdjTypeSuffix()    { return declSuffices.get(DeclSuffixKey.ADJ_DER_SUFFIX); }
	public static DeclensionResult getEinAdjTypeSuffix()    { return declSuffices.get(DeclSuffixKey.ADJ_EIN_SUFFIX); }
	public static DeclensionResult getStrongAdjTypeSuffix() { return declSuffices.get(DeclSuffixKey.ADJ_STARKE_SUFFIX); }
	public static DeclensionResult getProssessiveSuffix()   { return declSuffices.get(DeclSuffixKey.POSSESSIVE_SUFFIX); }
	public static DeclensionResult getPersonalPronouns()    { return declSuffices.get(DeclSuffixKey.PERSONAL_PRONOUNS); }
	public static DeclensionResult getReflexivePronouns()   { return declSuffices.get(DeclSuffixKey.REFLEXIVE_PRONOUNS); }

	private enum DeclensionRuleKey {
		DER_WORD,
		DER_WORD_ALT,
		EIN_WORD,
		POSS_PRON,
		MAND,
		REL_PRON,
		DER_ADJ,
		EIN_ADJ,
		STRONG_ADJ,
		PERSONAL_PRON,
		REFLEXIVE_PRON,
		ADJ
	};
	private enum Persons {
		p (false, false, false, true ), // plural
		s (true,  true,  true,  false), // all single
		m (true,  false, false, false), // only single masculine
		a (true,  true,  true,  true ); // all per suffix dimension, usually m/f/n/p.
		
		boolean[] includes;
		Persons(boolean sm, boolean sf, boolean sn, boolean pl) {
			includes = new boolean[]{ sm, sf, sn, pl };
		}
	};

	private static final HashMap<String, WordDeclension> wordDeclensions = new HashMap<String, WordDeclension>();

	private static class DeclensionRule {
		String usage;
		DeclensionRuleKey[] keys;
		String[] roots;
		Persons persons;
		
		boolean[] getIncludes() { return persons == null ? null : persons.includes; }
	}

	private static class WordDeclension {
		String word;
		DeclensionRule[] rules;

		WordDeclension(String word, DeclensionRule[] rules) {
			this.word = word;
			this.rules = rules;
		}
	}
	
	private static DeclensionResult[] decline(WordDeclension wd) {
		int i, j;
		ArrayList<DeclensionResult> ret = new ArrayList<DeclensionResult>();

		for (i=0; i<wd.rules.length; ++i) {
			DeclensionRule dr = wd.rules[i];
			for (j=0; j<dr.keys.length; ++j) {
				String root = at(dr.roots, j, wd.word);
				DeclensionRuleKey drk = dr.keys[j];
				boolean[] includes = dr.getIncludes();
				DeclensionResult cur = null;
				switch(drk) {
				case REL_PRON:       cur = declineDerWord(root, dr.usage, true,  false, includes); break;
				case DER_WORD:       cur = declineDerWord(root, dr.usage, false, false, includes); break;
				case DER_WORD_ALT:   cur = declineDerWord(root, dr.usage, false, true, includes); break;
				case EIN_WORD:       cur = declineEinWord(root, dr.usage, includes); break;
				case POSS_PRON:      cur = declineOneSet(root, getProssessiveSuffix(), dr.usage, includes); break;
				case MAND:           cur = declineOneSet(root, getMandSuffix(), dr.usage, includes); break;
				case DER_ADJ:        cur = declineOneSet(root, getDerAdjTypeSuffix(), dr.usage, includes); break;
				case EIN_ADJ:        cur = declineOneSet(root, getEinAdjTypeSuffix(), dr.usage, includes); break;
				case STRONG_ADJ:     cur = declineOneSet(root, getStrongAdjTypeSuffix(), dr.usage, includes); break;
				case PERSONAL_PRON:  cur = listPersonalPronouns(false, dr.usage); break;
				case REFLEXIVE_PRON: cur = listPersonalPronouns(true, dr.usage); break;
				case ADJ:            ret.add(declineOneSet(root, getDerAdjTypeSuffix(), ADJ_DER_USAGE, includes));
									 ret.add(declineOneSet(root, getEinAdjTypeSuffix(), ADJ_EIN_USAGE, includes));
									 cur = declineOneSet(root, getStrongAdjTypeSuffix(), ADJ_STRONG_USAGE, includes);
				                     break;
				}
				ret.add(cur);
			}
		}

		return ret.toArray(new DeclensionResult[ret.size()]);
	}

	public static DeclensionResult[] declineAdjective(String adj) {
		return new DeclensionResult[]{
				declineOneSet(adj, getDerAdjTypeSuffix(), ADJ_DER_USAGE, null),
				declineOneSet(adj, getEinAdjTypeSuffix(), ADJ_EIN_USAGE, null),
				declineOneSet(adj, getStrongAdjTypeSuffix(), ADJ_STRONG_USAGE, null)
		};
	}

	public static DeclensionResult declineEinWord(String root, String usage, boolean[] includes) {
		DeclensionResult ret = declineOneSet(root, getEinTypeSuffix(), usage, includes);
		if (root.equals("ein")) {
			for (String[] sa : ret.getResults(LANG.de)) {
				if (length(sa) >= 4)
					sa[3] = "k" + sa[3];
			}
		}
		return ret;
	}

	private static DeclensionResult declineDerWord(String word, String usage, boolean isRelativePron, boolean hasAlternative, boolean[] includes) {
		DeclensionResult ret;
		String[] sa;

		if (word.endsWith("er"))
			word = chopRight(word, 2);
		ret = declineOneSet(word, word.equals("d") ? getDerSuffix() : getDerTypeSuffix(), usage, includes);

		if (isRelativePron) {
			// genitive exception
			sa = ret.getGenitive();
			if (sa != null) {
				for (int i=0; i<sa.length; ++i)
					sa[i] += sa[i].endsWith("s") ? "sen" : "en";
			}

			// dative plural exception
			sa = ret.getDative();
			if (sa != null && sa.length >= 4)
				sa[3] += "en";
		}

		if (hasAlternative) {
			sa = ret.getGenitive();
			sa[0] = chopRight(sa[0], 2) + "e(s/n)"; 
			if (sa.length > 2)
				sa[2] = sa[0];
		}

		return ret;
	}

	public static DeclensionResult declineOneSet(String word, DeclensionResult suffices, String usage, boolean[] includes) {
		String[] nom = appendSansDupE(word, suffices.getNominative(), includes);
		String[] gen = appendSansDupE(word, suffices.getGenitive(),   includes);
		String[] dat = appendSansDupE(word, suffices.getDative(),     includes);
		String[] akk = appendSansDupE(word, suffices.getAccusative(), includes);
		return new DeclensionResult(usage, nom, gen, dat, akk, pick(MFNP_NAMES, includes));
	}

	private static DeclensionResult listPersonalPronouns(boolean isReflexive, String usage) {
		final String[] titles = { "S1", "S2", "S3m", "S3f", "S3nt", "P1", "P2", "P3" };
		DeclensionResult ret = new DeclensionResult(usage, titles);
		ret.copyDeclensionResult(isReflexive ? getReflexivePronouns() : getPersonalPronouns(), false);
		return ret;
	}

	static {
		try {
			// read adjective rules
			Properties props = readPropertiesFromCP(DE_ADJEKTIVUNDADVERB_FILE);

			// SingleSyllableComparativeWithoutUmlaut
			String x = props.getProperty(AdjectiveAndAdverbKey.SingleSyllableComparativeWithoutUmlaut.name());
			String[] sa = x.split(",");
			trim(sa);
			enAccent(sa);
			for (String s : sa) {
				if (isNotEmpty(s))
					singleSyllableComparativeWithoutUmlaut.add(s);
			}

			// SuperlativeSpecialEnding
			x = props.getProperty(AdjectiveAndAdverbKey.SuperlativeSpecialEnding.name());
			sa = x.split(",");
			trim(sa);
			for (String s : sa) {
				String[] tmp = s.split("\\|");
				enAccent(tmp);
				superlativeSpecialEnding.put(tmp[0], tmp[1]);
			}

			// ComparativeSpecialEnding
			x = props.getProperty(AdjectiveAndAdverbKey.ComparativeSpecialEnding.name());
			sa = x.split(",");
			trim(sa);
			for (String s : sa) {
				String[] tmp = s.split("\\|");
				enAccent(tmp);
				comparativeSpecialEnding.put(tmp[0], tmp[1]);
			}
			
			// IrregularComparatives
			x = props.getProperty(AdjectiveAndAdverbKey.IrregularComparatives.name());
			sa = x.split(",");
			trim(sa);
			for (String s : sa) {
				if (isEmpty(s))
					continue;
				String[] irreg = s.split("\\|");
				if (irreg.length != 3)
					continue;
				trim(irreg);
				enAccent(irreg);
				String superI = irreg[2].replaceAll("_", " ");
				irregularComparative.put(irreg[0], new String[]{ irreg[1], superI, "am " + superI + 'n' });
			}

			// Read pronoun/article/etc rules
			int i, k;
			JSONObject json;
			String jsonStr = readFileFromCPAsString(DE_PRONOMARTIKELUSW_FILE);
    		JSONObject jsonTop = new JSONObject(jsonStr);

    		// Read SUFFICES
    		String[] nom;
    		String[] gen;
    		String[] dat;
    		String[] akk;

    		JSONArray jsonArr = jsonTop.getJSONArray("SUFFICES");
    		for (i=jsonArr.length()-1; i>=0; --i) {
    			json = jsonArr.getJSONObject(i);
    			String name = json.getString("name");
    			nom = gen = dat = akk = null;
    			for (String s : getStringArray(json, "suffices")) {
    				sa = s.split(",");
    				trim(sa);
    				x = sa[0].toLowerCase();
    				String[] sa1 = Arrays.copyOfRange(sa, 1, sa.length);
    				if      (x.startsWith("nom")) nom = sa1;
    				else if (x.startsWith("gen")) gen = sa1;
    				else if (x.startsWith("dat")) dat = sa1;
    				else if (x.startsWith("akk") || x.startsWith("acc")) akk = sa1;
    			}
    			DeclSuffixKey key = DeclSuffixKey.valueOf(name);
    			String defaultUsage = json.optString("default_usage", null);
				declSuffices.put(key, new DeclensionResult(defaultUsage, nom, gen, dat, akk, null));
    		}

    		// Read WORDS:
    		jsonArr = jsonTop.getJSONArray("WORDS");
    		for (i=jsonArr.length()-1; i>=0; --i) {
    			json = jsonArr.getJSONObject(i);

    			String[] words = getStringArray(json, "word");
    			enAccent(words);
    			String[] variants = getStringArray(json, "variants"); // only for words.length == 1
    			enAccent(variants);
    			String[][] rules; // String[][2 or 3]: each rule is [usage, decl, persons].

    			Object obj = json.opt("rules");
    			JSONObject[] jsons;
    			if (obj instanceof JSONArray) {
    				JSONArray arr = (JSONArray)obj;
    				jsons = new JSONObject[arr.length()];
    				for (k=arr.length()-1; k>=0; --k)
    					jsons[k] = arr.getJSONObject(k);
    			} else {
    				jsons = new JSONObject[]{ (JSONObject)obj };
    			}
    			rules = new String[jsons.length][];
    			for (k=jsons.length-1; k>=0; --k) {
    				json = jsons[k];
    				sa = new String[json.has("persons") ? 3 : 2];
    				sa[0] = json.getString("usage");
    				sa[1] = json.getString("declension");
    				if (sa.length > 2)
    					sa[2] = json.getString("persons");
    				rules[k] = sa;
    			}
    			addWord(words, variants, rules);
    		}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

	private static void addWord(String[] words, String[] variants, String[][] rules) {
		DeclensionRule[] declensionRules = new DeclensionRule[rules.length];
		for (int i=0; i<rules.length; ++i) {
			DeclensionRule dr = new DeclensionRule();
			dr.usage = rules[i][0];

			String x = rules[i][1];
			String[] sa = x.split(",");
			dr.keys = new DeclensionRuleKey[sa.length];
			dr.roots = new String[sa.length];
			for (int j=0; j<sa.length; ++j) {
				x = sa[j];
				int idx = x.indexOf('<');
				String key = x;
				String root = null;
				if (idx > 0) {
					key = x.substring(0, idx);
					x = x.substring(idx+1);
					idx = x.indexOf('>');
					root = x.substring(0, idx).trim();
				}
				dr.keys[j] = DeclensionRuleKey.valueOf(key.trim());
				dr.roots[j] = root;
			}
			
			String persons = at(rules[i], 2);
			if (persons != null)
				dr.persons = Persons.valueOf(persons);
			else
				dr.persons = Persons.a;

			declensionRules[i] = dr;
		}

		WordDeclension wd = null;
		for (String w : words) {
			wd = new WordDeclension(w, declensionRules);
			wordDeclensions.put(w, wd);
		}
		if (variants != null && words.length == 1) {
			for (String v : variants)
				wordDeclensions.put(v, wd);
		}
	}
}